/**
 * Copyright (c) <2010>, <SUNNY GUPTA>
 * All rights reserved.

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the <ORGANIZATION> nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <SUNNY GUPTA> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.gwtp.investmentadvisor.client.gui.authentication;

import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasBlurHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.HasValue;
import com.google.inject.Inject;
import com.gwtp.investmentadvisor.client.dispatch.DispatchCallback;
import com.gwtp.investmentadvisor.client.place.PlaceNameTokens;
import com.gwtp.investmentadvisor.shared.action.NewUserSignUpAttempt;
import com.gwtp.investmentadvisor.shared.action.NewUserSignUpAttemptResult;
import com.gwtp.investmentadvisor.shared.action.UsernameAvailabilityCheck;
import com.gwtp.investmentadvisor.shared.action.UsernameAvailabilityCheckResult;
import com.gwtp.investmentadvisor.shared.entities.NewUserInfo;
import com.gwtp.investmentadvisor.shared.event.NewUserSignUpAttemptCompleteEvent;
import com.gwtp.investmentadvisor.shared.event.NewUserSignUpAttemptCompleteEventHandler;
import com.gwtp.investmentadvisor.shared.event.NewUserSignUpSuccessfulEvent;
import com.gwtp.investmentadvisor.shared.event.UsernameAvailabilityCheckEvent;
import com.gwtp.investmentadvisor.shared.event.UsernameAvailabilityCheckEventHandler;
import com.gwtp.investmentadvisor.shared.utilities.Validator;
import com.gwtplatform.dispatch.client.DispatchAsync;
import com.gwtplatform.mvp.client.EventBus;
import com.gwtplatform.mvp.client.Presenter;
import com.gwtplatform.mvp.client.annotations.NameToken;
import com.gwtplatform.mvp.client.annotations.NoGatekeeper;
import com.gwtplatform.mvp.client.annotations.ProxyCodeSplit;
import com.gwtplatform.mvp.client.proxy.PlaceManager;
import com.gwtplatform.mvp.client.proxy.PlaceRequest;
import com.gwtplatform.mvp.client.proxy.RevealRootContentEvent;

/**
 * 
 * @author Sunny Gupta
 *
 */
public class SignUpScreenPresenter extends 
    Presenter<SignUpScreenPresenter.Display, SignUpScreenPresenter.Proxy> {
  
  /**
   * Display Interface that the View needs to implement.
   * @author Sunny Gupta
   *
   */
  public interface Display extends com.gwtplatform.mvp.client.View {
    // For signup button
    HasClickHandlers getSubmitButton();
    // For username text
    HasValue<String> getUsernameText();
    // For password text
    HasValue<String> getPasswordText();
    // For confirm username text
    HasValue<String> getConfirmUsernameText();
    // For confirm password text
    HasValue<String> getConfirmPasswordText();
    
    // For keypress events
    HasKeyPressHandlers getNameTextKeyPressProvider();
    HasKeyPressHandlers getUsernameTextKeyPressProvider();
    HasKeyPressHandlers getConfirmUsernameTextKeyPressProvider();
    HasKeyPressHandlers getPasswordTextKeyPressProvider();
    HasKeyPressHandlers getConfirmPasswordTextKeyPressProvider();

    // Event Handlers
    HasBlurHandlers getUsernameBlurEventProvider();
    HasBlurHandlers getConfirmUsernameBlurEventProvider();
    void showInvalidUsername();
    void showInvalidConfirmUsername();
    void showUsernameMismatch();
    void clearInvalidUsername();
    void clearInvalidConfirmUsername();
    void clearUsernameMismatch();
    
    // Button controls
    void enableSignUpButton();
    void disableSignUpButton();
    
    // Error messaging
    void showInvalidPassword();
    void clearInvalidPassword();
    void showInvalidConfirmPassword();
    void clearInvalidConfirmPassword();
    void showPasswordMismatch();
    void clearPasswordMismatch();
    HasValue<String> getNameText();
    void showUsernameAvailability(boolean isUsernameAvailable);
    void showUsernameAvailabilityCheckInProgress();
    void hideUsernameAvailabilityCheckInProgress();
    void showServerErrorMessage();
    void clearErrorMessage();
    void showSignUpFailure(String message);
  }

  /**
   * Proxy Interface. Extends Proxy<P> and Place to support Place Management
   * @author Sunny Gupta
   *
   */
  @ProxyCodeSplit
  @NoGatekeeper
  @NameToken(PlaceNameTokens.SignUpScreenNameToken)
  public interface Proxy extends 
    com.gwtplatform.mvp.client.proxy.ProxyPlace<SignUpScreenPresenter> {
  }

  private boolean checkUsernameMismatch;
  private boolean isUsernameAvailable;
  private DispatchAsync dispatcher;
  private final PlaceManager placeManager;
  
  @Inject
  public SignUpScreenPresenter(final Display view, final Proxy proxy, 
      final EventBus eventBus, final DispatchAsync dispatcher,
      final PlaceManager placeManager) {
    super(eventBus, view, proxy);
    
    // Initialize dispatcher
    this.dispatcher = dispatcher;
    
    // Initialize place manager
    this.placeManager = placeManager;
    
    // Initialize states
    this.checkUsernameMismatch = false;
    this.isUsernameAvailable = false;
    getView().disableSignUpButton();
    
    // Do the binding
    bind();
  }

  @Override
  protected void onBind() {
    super.onBind();
    
    // Obtain view
    final Display view = getView();
    
    // Add Event Handlers
    
    // Username Availability check event
    addRegisteredHandler(UsernameAvailabilityCheckEvent.TYPE, new UsernameAvailabilityCheckEventHandler() {
      
      @Override
      public void onUsernameAvailabilityCheckEventComplete(
          UsernameAvailabilityCheckEvent event) {
        // Check if matching username
        if (event.getUsernameAvailabilityCheckResult().getUsername().equals(view.getUsernameText().getValue())) {
          // Get result      
          isUsernameAvailable = event.getUsernameAvailabilityCheckResult().isAvailable();

          // Indicate to view
          view.showUsernameAvailability(isUsernameAvailable);
        }
      }
    });
    
    // Signup complete event
    addRegisteredHandler(NewUserSignUpAttemptCompleteEvent.TYPE, new NewUserSignUpAttemptCompleteEventHandler() {
      
      @Override
      public void onNewUserSignUpSuccessfulEventComplete(
          NewUserSignUpAttemptCompleteEvent event) {
        // Check if matching username
        if (event.getNewUserSignUpResult().getNewUserInfo().getEmail().equals(view.getUsernameText().getValue())) {
          // Get result      
          boolean isSignedUp = event.getNewUserSignUpResult().getSignUpAttempResult().isSignedUp();

          if (isSignedUp) {
            // Raise sign up successful event
            fireEvent(
                new NewUserSignUpSuccessfulEvent(
                    event.getNewUserSignUpResult().getNewUserInfo()));
            // Reveal Post Sign Up Place
            placeManager.revealPlace(new PlaceRequest(PlaceNameTokens.PostSignupScreenNameToken));
          } else {
            // Indicate failure
            view.showSignUpFailure(
                event.getNewUserSignUpResult().getSignUpAttempResult().getReason().toString());
          }
        }
      }
    });
  
    // Submit Button
    registerHandler(view.getSubmitButton().addClickHandler(new ClickHandler() {
      
      @Override
      public void onClick(ClickEvent event) {
        doSignUp();
      }
    }));
    
    // Key Press Handlers
    // Scope for optimization by listening to container keypress...think about it
    registerHandler(view.getNameTextKeyPressProvider().addKeyPressHandler(new KeyPressHandler() {
      
      @Override
      public void onKeyPress(KeyPressEvent event) {
        // Clear error message if any
        view.clearErrorMessage();
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    registerHandler(view.getUsernameTextKeyPressProvider().addKeyPressHandler(new KeyPressHandler() {
      
      @Override
      public void onKeyPress(KeyPressEvent event) {
        // Clear error message if any
        view.clearErrorMessage();
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    registerHandler(view.getConfirmUsernameTextKeyPressProvider().addKeyPressHandler(new KeyPressHandler() {
      
      @Override
      public void onKeyPress(KeyPressEvent event) {
        // Clear error message if any
        view.clearErrorMessage();
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    registerHandler(view.getPasswordTextKeyPressProvider().addKeyPressHandler(new KeyPressHandler() {
      
      @Override
      public void onKeyPress(KeyPressEvent event) {
        // Clear error message if any
        view.clearErrorMessage();
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    registerHandler(view.getConfirmPasswordTextKeyPressProvider().addKeyPressHandler(new KeyPressHandler() {
      
      @Override
      public void onKeyPress(KeyPressEvent event) {
        // Clear error message if any
        view.clearErrorMessage();
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    
    // Validate username
    registerHandler(view.getUsernameBlurEventProvider().addBlurHandler(new BlurHandler() {
      
      @Override
      public void onBlur(BlurEvent event) {
        validateUsername();
        
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    
    // Validate username
    registerHandler(view.getConfirmUsernameBlurEventProvider().addBlurHandler(new BlurHandler() {
      
      @Override
      public void onBlur(BlurEvent event) {
        validateUsername();
        
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    
    // To arrive at condition for mismatch check
    registerHandler(view.getUsernameText().addValueChangeHandler(new ValueChangeHandler<String>() {
      
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {
        // Reset
        checkUsernameMismatch = false;
      }
    }));
    
    // To arrive at condition for mismatch check
    registerHandler(view.getConfirmUsernameText().addValueChangeHandler(new ValueChangeHandler<String>() {
      
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {
        // Set
        checkUsernameMismatch = true;
      }
    }));
    
    // Validate password
    registerHandler(view.getPasswordText().addValueChangeHandler(new ValueChangeHandler<String>() {
      
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {      
        // Check password
        String password = view.getPasswordText().getValue();
        
        if (password.length() < UserAuthenticationConstants.MINIMUM_PASSWORD_LENGTH) {
          view.showInvalidPassword();
        } else {
          view.clearInvalidPassword();
        }
        
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
    
    // To arrive at condition for mismatch check
    registerHandler(view.getConfirmPasswordText().addValueChangeHandler(new ValueChangeHandler<String>() {
      
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {        
        // Check confirm password
        String password = view.getPasswordText().getValue();
        String confirmPassword = view.getConfirmPasswordText().getValue();
        
        if (confirmPassword.length() < UserAuthenticationConstants.MINIMUM_PASSWORD_LENGTH) {
          view.showInvalidConfirmPassword();
        } else {
          view.clearInvalidConfirmPassword();
        }
        
        if (!password.equals(confirmPassword)) {
          view.showPasswordMismatch();
        } else {
          view.clearPasswordMismatch();
        }
        
        // Enable signup button if possible
        enableSignUpButtonIfPossible();
      }
    }));
  }

  protected void doSignUp() {
    // Log.info("Authenticating Login Info");
    
    // Obtain view
    Display view = getView();
    
    // For UserInfo
    NewUserInfo newUserInfo = new NewUserInfo(getView().getNameText().getValue(), 
        view.getUsernameText().getValue(), 
        view.getPasswordText().getValue());
    
    // Call dispatch
    dispatcher.execute(new NewUserSignUpAttempt(newUserInfo), 
        new DispatchCallback<NewUserSignUpAttemptResult>() {

          @Override
          public void callback(NewUserSignUpAttemptResult result) {
            // Take the result from the server and notify client interested components
            fireEvent(new NewUserSignUpAttemptCompleteEvent(result));
          }
    });
  }

  protected void validateUsername() {
    boolean isUsernameAvailabilityCheckNeeded = true;
    
    // Obtain view
    Display view = getView();
    
    String username = view.getUsernameText().getValue();
    String confirmUsername = view.getConfirmUsernameText().getValue();

    if (username.length() > 0 && !Validator.isValidEmailAddress(username)) {
      // Show error
      view.showInvalidUsername();
      isUsernameAvailabilityCheckNeeded = false;
    } else {
      // Clear error
      view.clearInvalidUsername();
    }
    
    if (confirmUsername.length() > 0 && !Validator.isValidEmailAddress(confirmUsername)) {
      // Show error
      view.showInvalidConfirmUsername();
      isUsernameAvailabilityCheckNeeded = false;
    } else {
      // Clear error
      view.clearInvalidConfirmUsername();
    }
    
    // Check match only when valid
    if (this.checkUsernameMismatch) {
      if (!username.equals(confirmUsername)) {
        isUsernameAvailabilityCheckNeeded = false;
        view.showUsernameMismatch();
      } else {
        // Clear error
        view.clearUsernameMismatch();
      }
    } else {
      // Clear isUsernameAvailabilityCheckNeeded as it cannot be the case
      isUsernameAvailabilityCheckNeeded = false;
    }
    
    if (isUsernameAvailabilityCheckNeeded) {
      doUsernameAvailabilityCheck();
    }
  }

  private void doUsernameAvailabilityCheck() {
    // Log.info("Checking username availability");
    
    // Obtain view
    final Display view = getView();
    
    // Indicate view
    view.showUsernameAvailabilityCheckInProgress();
    
    // Dispatch action
    dispatcher.execute(new UsernameAvailabilityCheck(view.getUsernameText().getValue()), 
        new DispatchCallback<UsernameAvailabilityCheckResult>() {

          /**
           * Need to override callbackError since this is a background action
           */
          @Override
          public void callbackError(Throwable caught) {
            // Log.error("Handle Failure:", caught);

            // Since this is a background action, need to manage error in passive manner
            view.showServerErrorMessage();
            
            // Hide processing
            view.hideUsernameAvailabilityCheckInProgress();
          }

          @Override
          public void callback(UsernameAvailabilityCheckResult result) {
            // Take the result from the server and notify client interested components
            fireEvent(new UsernameAvailabilityCheckEvent(result));
          }
    });
  }

  private void enableSignUpButtonIfPossible() {
    // Obtain view
    Display view = getView();
    
    // Check all conditions
    String name = view.getNameText().getValue();
    String username = view.getUsernameText().getValue();
    String confirmUsername = view.getConfirmUsernameText().getValue();
    String password = view.getPasswordText().getValue();
    String confirmPassword = view.getConfirmPasswordText().getValue();
    
    // Leverage short circuiting
    if (this.isUsernameAvailable &&
        Validator.isValidEmailAddress(username) &&
        confirmUsername.equals(username) &&
        password.length() >= UserAuthenticationConstants.MINIMUM_PASSWORD_LENGTH &&
        confirmPassword.equals(password) &&
        name.length() > 0) {
      view.enableSignUpButton();
    } else {
      view.disableSignUpButton();
    }
  }

  @Override
  protected void revealInParent() {
    RevealRootContentEvent.fire(this, this);
  }
}
